﻿
using CodePlex.TfsPluginSuite.Configuration;
using CodePlex.TfsPluginSuite.Configuration.BranchMergePolicy;
using CodePlex.TfsPluginSuite.Helper;
using Microsoft.TeamFoundation.Common;
using Microsoft.TeamFoundation.Framework.Server;
using Microsoft.TeamFoundation.VersionControl.Server;
using System;
using System.Text.RegularExpressions;

namespace CodePlex.TfsPluginSuite.Policies
{
    public class BranchMergeCheckInPolicy : ISubscriber
    {
        public string Name
        {
            get { return typeof(BranchMergeCheckInPolicy).FullName; }
        }

        public SubscriberPriority Priority
        {
            get { return SubscriberPriority.Normal; }
        }

        //When debuging, copy .dlls and .pdb 
        //to %Program Files%\Microsoft Team Foundation Server 2010\Application Tier\Web Services\bin\Plugins
        public EventNotificationStatus ProcessEvent(IVssRequestContext requestContext,
            NotificationType notificationType,
            object notificationEventArgs,
            out int statusCode,
            out string statusMessage,
            out ExceptionPropertyCollection properties)
        {
            EventNotificationStatus response = EventNotificationStatus.ActionPermitted;
            properties = new ExceptionPropertyCollection();
            statusMessage = String.Empty;
            statusCode = 0;

            // If this is a Check-in notification and is a decision point and this policy is enabled
            if (notificationEventArgs is CheckinNotification
                && notificationType == NotificationType.DecisionPoint
                && TfsPluginSuiteConfigurationManager.Section.BranchMergePolicy.Enabled)
            {
                try
                {
                    CheckinNotification notification = notificationEventArgs as CheckinNotification;

                    bool matchTeamProject = TfsPluginSuiteConfigurationManager.Section.BranchMergePolicy.MatchTeamProjects;

                    //evaluate first class branch
                    if (notification.CheckinType == CheckinType.Branch)
                    {
                        string sourceBranch = requestContext.Method.Parameters["sourcePath"];
                        string targetBranch = requestContext.Method.Parameters["targetPath"];
                        statusMessage = EvaluateBranch(sourceBranch, targetBranch, matchTeamProject);
                        if (!string.IsNullOrEmpty(statusMessage))
                        {
                            properties.Set("sourceBranch", sourceBranch);
                            properties.Set("targetBranch", targetBranch);
                            response = EventNotificationStatus.ActionDenied;
                        }
                    }
                    //evaluate non-first class branches
                    else if (notification.CheckinType == CheckinType.Workspace)
                    {
                        using (var tfsHelper = new TfsHelper(requestContext, notification))
                        {
                            foreach (var change in tfsHelper.GetPendingChanges())
                            {
                                var changeType = (ChangeType)change.ChangeEx;
                                //evaluate for folder branch
                                if (changeType.HasFlag(ChangeType.Branch))
                                {
                                    string errors = EvaluateBranch(change.SourceServerItem, change.ServerItem, matchTeamProject);
                                    if (!string.IsNullOrEmpty(errors))
                                    {
                                        statusMessage = errors;
                                        properties.Set("item path", change.ServerItem);
                                        return EventNotificationStatus.ActionDenied; //can only handle one error at a time with server side policies
                                    }
                                }
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    statusCode = -1;
                    statusMessage += e.ToString();
                    requestContext.Status = e;
                    response = EventNotificationStatus.ActionDenied;
                }
            }
            return response;
        }


        public Type[] SubscribedTypes()
        {
            return new Type[] { typeof(CheckinNotification) };
        }

        private static string EvaluateBranch(string sourcePath, string targetPath, bool matchTeamProj)
        {
            //check team project
            if (TfsPluginSuiteConfigurationManager.Section.BranchMergePolicy.MatchTeamProjects)
            {
                var sourceTeamProject = GetTeamProjectNameFromPath(sourcePath);
                var targetTeamProject = GetTeamProjectNameFromPath(targetPath);

                if (!sourceTeamProject.Equals(targetTeamProject))
                {
                    return string.Format("Cannot branch from team project [{0}] to [{1}]", sourceTeamProject, targetTeamProject);
                }
            }

            foreach (SourcePathConfigurationElement sourcePathConfig in TfsPluginSuiteConfigurationManager.Section.BranchMergePolicy)
            {
                //if the path matches, then evaluate  
                if (Regex.IsMatch(sourcePath, sourcePathConfig.PathRegex))
                {
                    if (sourcePathConfig.IsUnrestricted)
                    {
                        //free ride!! woohoo!!!
                        return string.Empty;
                    }
                    else
                    {
                        foreach (TargetPathConfigurationElement targetPathConfig in sourcePathConfig)
                        {
                            if (Regex.IsMatch(targetPath, targetPathConfig.PathRegex))
                            {
                                //safe!! woohoo!!!     
                                return string.Empty;
                            }
                        }
                        //if we got here, there are no valid target paths
                        return string.Format("[{0}] is an invalid target branch from [{1}]", targetPath, sourcePath);
                    }
                }
            }
            //if we got here, there are no valid source paths
            return string.Format("Cannot branch from [{0}] to [{1}].", sourcePath, targetPath);
        }

        private static string GetTeamProjectNameFromPath(string path)
        {
            var folders = path.Split('/');
            return folders[1];
        }
    }
}
